Rhapsody Developer Release Copyright 1997 by Apple Computer, Inc. All Rights Reserved.
This file provides application developers with the information they need to develop localized versions of their applications and localizers with the information they need to translate the localizable resources in an application.
Localization is the process of making your application language-independent in such a way that a user can choose a version of your application that is specific, in its text, sound, and images, to any of a number of languages that you make available. The executable used for all localized versions of the application is the same.
The process of localizing your application consists of two distinct phases:
Even if you don't have immediate plans to support multiple languages in your application, there are advantages to designing your application so that it provides support for localization: With proper design, your application's source code won't have to be touched in order for the application to be localized; therefore, you won't run the risk of introducing additional bugs by putting the necessary hooks in later. Second, you can test the localization code along with the initial monolingual product, so the amount of testing needed for any future localized version will be minimized.
The Yellow Box uses Unicode as its main character encoding, and Unicode makes it possible to represent most of the languages of the world. However, the text system currently does not support some writing systems (vertical and right-to-left), so it's not practical to localize to those languages. The general overall goal for developers shoud be, at least in the earlier releases of Rhapsody, to ensure that applications are developed in a localizable fashion.
The Preferences application lets users prioritize (that is, set the order of) the languages they prefer to use applications in. If your application isn't localized, users can only use it in the language you wrote it in, no matter how their language preferences are set in Preferences. In order to localize your application, start by placing in external, editable files all the language-specific and culture-specific text, images, and sounds in your application. Then have those files translated into whatever additional languages you wish to support.
Once your application contains translated versions of the externalized language-specific files, it can load all the necessary language-specific information from the appropriate set of files, based on the user's language preferences. Thus, your application automatically presents itself to each user in that user's preferred language—ideally (but not necessarily) the user's first choice. For example, assume that one user has given the default system languages the order:
1 English
2 Spanish
3 German
4 French
5 Italian
6 Swedish
7 Japanese
8 Portuguese
and another user has given them the order:
1 Spanish
2 French
3 German
4 English
5 Italian
6 Swedish
7 Japanese
8 Portuguese
If your application supported just English and French localizations, then your application would appear to the first user in English (that user's first choice) and to the second user in French (that user's second choice).
A Yellow Box application is organized as a file package , a special type of directory that looks and behaves like a file to users. The file package contains the executable file, plus any additional files (typically called resources ) associated with the application.
The file package organization is useful because it gives the developer a place to store external files used by the application. This is essential when you localize your application, because you need to create one language .lproj subdirectory for each language that you want your application to support; each language .lproj subdirectory will contain the language-specific and culture-specific files used by your application.
As an example, here's what the TextEdit application looks like when installed on the system. Note that in this example it hasn't been localized; it contains only the English localization, which is what the developer chose to develop the application with:
TextEdit.app/ What the user sees (shown as a single file) TextEdit The executable itself Resources/ Resources directory Edit.tiff Non-localizable resources EditTitle.tiff ... Encodings.txt ... Info-nextstep.plist ... EditApp.ico ... English.lproj/ The localizable resources (in English) Localizable.strings String translations FindPanel.strings String translations for the Find pane Document.nib/ Nib files (describing panels, windows) FindPanel.nib/ ... Preferences.nib/ ... EncodingAccessory.nib/ ... Info.nib/ ... Edit.nib/ ... Edit-windows.nib/ Windows-specific version of the above nib file Help.plist Help file
If TextEdit were to be localized into French, then a French.lproj directory containing the same files as English.lproj would be placed in the Resources directory, as a peer to English.lproj. Typically English.lproj is sent to the translators, and they send back language.lproj directories for each translation, containing localized versions of each resource in the directory.
There are two main tools used in localization: You can edit srings files with any editor, but preferably one that can save Unicode files (such as TextEdit). You nib files with Interface Builder. You edit other resources (help files, sounds, etc) with an appropriate tool.
Although it's useful to understand the structure of the application wrapper and its language-specific subdirectories, you don't have to worry about creating and maintaining the correct directory structure. The Project Builder application manages all these details for you. You must do a few things in Project Builder to localize files:
One other interesting point from the TextEdit example shown above is that it's possible to create system-specific resources that are used on a given platform. In the above example, a Windows-specific version of the Edit.nib resource has been provided (Edit-windows.nib). When running on Windows, this file will be used in place of Edit.nib. Platform specific files can be created by prefixing the resource name with -windows or -macintosh. If these interface files have localizable content, they should be localized just like the others.
Finally you should note that the NSBundle class in Foundation provides methods that enable access to resources in a bundle (an application's file package is a kind of bundle). NSBundle takes care of extracting the correctly localized and platform-specific version of a resource. If you need to access your own custom resources in a file package, or if you need to create your own bundle of resources for any purpose, NSBundle provides convenient ways to do these things.
Nib files store the user interface of an application, including windows, panels, and user interface elements such as buttons, sliders, text objects, tooltips for these elements; it also holds the connections between these objects that cause actions to be performed when the user activates controls. Nib files are typically localized wholesale; the localizer takes a nib file, translates all the user-visible strings and makes other adjustments as necessary (such as resizing the UI elements).
In any medium or large application, it's usually a good idea to put each window or panel in its own nib file. This not only allows the user interface to be lazily loaded (that is, as necessary), but it also permits localization to progress in more incremental steps. It's also a good idea to put the menu of the application in a separate nib file. This allows a different menu to be created for each software platform (Windows vs Macintosh, for instance).
Strings files enable you to externalize and localize the strings that are embedded in your application's source code. Strings that appear in the user interface and that aren't dynamically generated can typically be localized in the nib files themselves, so you don't need to put these in strings files.
Also keep in mind that there are two kinds of embedded strings: those that the user sees, and those that the user doesn't see. An example of a string the user doesn't see is contained in the following C statement:
matches = sscanf(s, "%d %d %s", &first, &last, &other);
The string "%d %d %s" does not need to be localized, since the user never sees it and it has no effect on anything that the user does see. On the other hand, a string which appears in an alert panel should be localized.
Place strings that need to be localized in a strings file, whose format is illustrated here:
/* A comment */ "Yes" = "Oui"; "Some string in English" = "Same string in French";
The string on the left is used as a key in code for locating the string on the right in the strings file. Internally, applications use the following macros (declared in Foundation/NSBundle.h) to extract strings out of a strings file:
NSLocalizedString(
key , comment)
key , table , comment
NSLocalizedStringFromTable ()
For instance, assuming the French localization was selected,
NSLocalizedString (@"Yes", @"")
would return "Oui" from the above table.
The arguments to the above macros are:
You can use keys that have formatting characters in them, to be
used as format strings into stringWithFormat:
. For
instance:
"Windows must have at least %d columns and %d rows." = "Les fentres doivent tre composes de %d colonnes et %d lignes au minimum."; "File %@ not found." = "Fichier %@ n'existe pas.";
The "%@" specifier is an extension to the standard printf() formatting characters. It makes it possible to represent arbitrary objects, which of course includes strings. Given that the Yellow Box APIs use string objects in place of C-strings, you should probably use "%@" instead of "%s" in most situations.
It's possible for the localizer to reorder the arguments if necessary (see below).
Although you can create the strings files by hand, it's also possible to generate them automatically from your source code.
The command-line program genstrings can help you generate
strings files automatically. The program works by parsing the source
files that you specify, extracting the information from each call to
the above macros, and adding that information to the appropriate
strings file. Every entry generated from a call to
NSLocalizedString()
is placed in a file called
Localizable.strings and every entry generated from a call to
NSLocalizedStringFromTable()
is placed in a file called
table.strings. Using separate tables creates
separate domains for sets of strings, allowing different translations
of the same string depending on the context.
The comment provided in these calls is also written out the
strings file, allowing the translator to get a better idea of what
the string is used for. It's important to understand that
genstrings outputs one entry for each call to
NSLocalizedString()
or
NSLocalizedStringFromTable()
, and duplicates any
identical entries. Therefore, avoid calling either of these functions
more than once with the same arguments. In particular, each key must
be unique (that is, not occurring more than once in a string file).
Whether you want to generate strings files automatically or create them by hand is up to you. In some cases you might find it convenient to generate your strings files once in the lifetime of your application development, then tweak it by hand. However, it's recommended that you avoid this, allowing you to generate and update the strings files whenever necessary.
You can use two defaults to help you detect localization problems with strings. You can specify these defaults on the command line (like other defaults) during testing; for instance:
TextEdit.app/TextEdit -NSShowNonLocalizableStrings YES
You use the default NSShowNonLocalizableStrings
to
catch strings that are not localizable. The strings are logged to the
shell in upper case. Although there might be some false positives,
this is still a useful technique.
You use the default NSShowNonLocalizedStrings
to
locate strings that were meant to be localized but could not be found
in the strings files. You can use this default to catch problems with
out-of-date localizations.
User-interface objects such as cells and text fields localize their content automatically when they parse or format data. Thus if you ask a cell for its numerical value, and the user's locale is French, "," will be used as the decimal separator. Similarly, if you display a floating point number in cells, table views, and so on, they will get localized automatically.
However, if you are formatting or scanning dates or numbers yourself using low-level objects such as NSString, NSDate, or NSScanner, you need to decide whether to localize data yourself. The following methods prompt this decision by providing an explicit locale argument:
NSString: - (id)initWithFormat:(NSString *)format locale:(NSDictionary *)dict, ...; + (id)stringWithFormat:(NSString *)format, ...; + (id)localizedStringWithFormat:(NSString *)format, ...; NSScanner: - (void)setLocale:(NSDictionary *)dict; + (id)scannerWithString:(NSString *)string; + (id)localizedScannerWithString:(NSString *)string; NSObject: - (NSString *)description; - (NSString *)descriptionWithLocale:(NSDictionary *)locale; NSDate: - (id)initWithString:(NSString *)description calendarFormat:(NSString *)format locale:(NSDictionary *)dict;
A locale is represented as a dictionary, using key/value pairs to
store information about how the localization should be performed.
Some of the possible keys in this dictionary are listed in
Foundation/NSUserDefaults.h. Typically you pass one of two
arguments as a locale dictionary: nil
(indicating
non-localized or canonical) or
[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]
which indicates the user's default localization (typically their first language choice). This statement returns a dictionary that contains a flattened view of the user's defaults and languages in order of user preference. The user's defaults take precedence, so any language-specific information might be overridden by entries in defaults (which are typically set from user's preferences).
The Application Kit makes localization easier by providing cover methods that ask for a "localized" version of an object, such as:
- (id)initWithFormat:(NSString *)format locale:(NSDictionary *)dict, ...; + (id)localizedStringWithFormat:(NSString *)format, ...;
The second method calls the first one with a locale argument of
[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] .
In versions of these methods without a locale argument, the processing is done in a non-localized manner. Future versions of the Yellow Box APIs will include more support for creating and manipulating locales from language and geographic files.
Strings files generated by genstrings have content that looks like this:
/* Comment */ "key" = "key";
That is, the value string is the same as the key string.
Translators should type the localized version of the key string in place of the second string. Use the comment, if necessary, to understand the context in which the string is displayed to the user:
/* Comment */ "key" = "French version of the key";
If a string contains multiple variable arguments, you can change the order of the arguments by using the "$" modifier plus the argument number:
/* Message in alert panel when something fails */ "Oh %@! %@ failed!" = "%2$@ blah blah, %1$@ oh!";
Just as in C, some characters must be prefixed with a backslash to be included in the string properly. These characters include double-quote, backslash, and carriage return. You can also specify carriage returns with "\n":
"File \"%@\" cannot be opened" = " ... "; "Type \"OK\" when done" = " ... ";
Strings can include arbitrary Unicode characters with "\U" followed by up to four hexadecimal digits denoting the Unicode character; for instance, space, which is hexadecimal 20, is represented as "\U0020". This option is useful if strings must include Unicode characters which cannot be typed for some reason.
Strings files are best saved in Unicode format. This allows them to be encoding-independent, and simplifies the encoding to use when an application loads strings files. The TextEdit application can save in Unicode format. The encoding can be selected either from the Save panel, or as a general preference in TextEdit's Preferences panel.
You use Interface Builder to create nib files, and you should use Interface Builder to localize nib files. Typically you open all of the nib files in a language.lproj directory, localize all the strings, change the sizes of UI elements if necessary, and save the nib files. There are a few things to watch out for: